import { computed, reactive } from "vue";
import { KeyboardCode } from "@/utils/keyboard-code";

/**
 * 命令模式实现 撤销/重做的命令管理类
 * @author  韦胜健
 * @date    2020/4/30 12:08
 *
 * 1. const commander = useCommander() 得到 commander对象；
 * 2. state.commander.register(command:Command) 注册命令
 * 3. commander.commands.smaller() 调用命令
 * 4. commander.isEnable.value.smaller 判断命令是否可用
 *
 */

interface CommandQueueItem {
  undo?: () => void;
  redo: () => void;
}

export class Command {
  name: string; // 命令名称
  execute: (...args: any[]) => CommandQueueItem; // 命令执行的逻辑
  keyboard?: undefined | string | string[]; // 监听的键盘事件：ctrl+shift+alt+a
  isEnable?: undefined | (() => boolean); // 判断当前是否可用
  isQueue?: boolean; // 是否遵循命令队列
  doNothingWhenExecute?: boolean; // 在调用execute的时候是否什么也不做 (dragend 的时候，这个位置的更新已经由graph做好了，这时候execute不需要立即执行redu)
  data?: undefined | any; // 命令缓存的数据
  init?: undefined | (() => void); // 命令初始化函数
  destroy?: undefined | (() => void); // 命令初始化函数
  graph?: undefined | any; // g6对象

  constructor(command: Command) {
    Object.assign(this, command);
    this.isEnable = command.isEnable == null ? () => true : command.isEnable;
    this.isQueue = command.isQueue == null ? true : command.isQueue;
    this.doNothingWhenExecute = command.doNothingWhenExecute == null ? false : command.doNothingWhenExecute;
    this.data = {};
  }
}

export function useCommander(editorState) {
  /**
   * 命令状态
   * @author  韦胜健
   * @date    2020/4/30 11:54
   */
  const state = reactive({
    queue: [] as CommandQueueItem[], // 当前执行过的命令队列
    index: -1, // 当前命令在命令队列中的索引
    registerCommands: [] as Command[], // 当前注册的命令
  });

  const commands: { [key: string]: (...args: any[]) => void } = {};

  const isEnable = computed(() => {
    return state.registerCommands.reduce((ret, item) => {
      ret[item.name] = item.isEnable();
      return ret;
    }, {});
  });

  /**
   * 注册新命令
   * @author  韦胜健
   * @date    2020/4/30 11:55
   */
  const register = (command: Command) => {
    if (!command.name) {
      console.log(command);
      throw new Error("Commander: command's name can not be empty!");
    }

    state.registerCommands.push(command);

    commands[command.name] = (...args: any[]) => {
      if (!!command.isEnable && !command.isEnable()) {
        return;
      }

      const { undo, redo } = command.execute(...args);

      if (!command.isQueue) {
        return !command.doNothingWhenExecute && redo();
      }

      let { queue, index } = state;
      if (queue.length > 0) {
        queue = queue.slice(0, index + 1);
        state.queue = queue;
      }
      queue.push({ undo, redo });
      state.index = index + 1;
      !command.doNothingWhenExecute && redo();
    };
  };

  register(
    new Command({
      name: "undo",
      keyboard: "ctrl+z",
      isQueue: false,
      execute: () => {
        return {
          redo: () => {
            if (state.index === -1) {
              return;
            }
            const queueItem = state.queue[state.index];
            // console.log('queueItem',queueItem)
            if (!!queueItem) {
              queueItem.undo();
              state.index--;
            }
          },
        };
      },
      isEnable: () => {
        if (editorState.props.disabledUndo) return false;
        return !!state.queue[state.index];
      },
    })
  );

  register(
    new Command({
      name: "redo",
      keyboard: "ctrl+shift+z",
      isQueue: false,
      execute: () => {
        return {
          redo: () => {
            const queueItem = state.queue[state.index + 1];
            if (!!queueItem) {
              queueItem.redo();
              state.index++;
            }
          },
        };
      },
      isEnable: () => {
        if (editorState.props.disabledUndo) return false;
        return !!state.queue[state.index + 1];
      },
    })
  );

  let onKeydown;

  function initEvent() {
    if (!onKeydown) {
      onKeydown = (e: KeyboardEvent) => {
        const names = [];
        e.ctrlKey && names.push("ctrl");
        e.shiftKey && names.push("shift");
        e.altKey && names.push("alt");

        names.push(KeyboardCode[e.keyCode]);
        const compositionKeyName = names.join("+");

        state.registerCommands.forEach((command: Command) => {
          if (!command.keyboard) return;
          const keys = Array.isArray(command.keyboard) ? command.keyboard : [command.keyboard];
          if (keys.indexOf(compositionKeyName) > -1) {
            e.stopPropagation();
            e.preventDefault();

            commands[command.name]();
          }
        });
      };
      window.addEventListener("keydown", onKeydown);
    }
  }

  function destroyEvent() {
    if (!!onKeydown) {
      window.removeEventListener("keydown", onKeydown);
      onKeydown = null;
    }
  }

  function init(graph: any) {
    state.registerCommands.forEach((command) => {
      command.graph = graph;
      if (!!command.init) {
        command.init();
      }
    });
  }

  function destroy() {
    destroyEvent();
    state.registerCommands.forEach((command) => {
      if (!!command.destroy) {
        command.destroy();
      }
    });
  }

  return {
    state,
    register,
    commands,
    isEnable,
    initEvent,
    destroyEvent,
    init,
    destroy,
  };
}
